package com.base.starter.config.shiro.jwt;

import cn.hutool.core.date.DateUtil;
import com.base.common.exception.GlobalException;
import com.base.common.utils.RedisUtils;
import com.base.domain.moudles.entity.User;
import com.base.service.UserService;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @author zhou minghao
 * @date 2024/4/23
 * @description
 */
@Slf4j
public class TokenServiceImpl implements TokenService{

    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private UserService userService;

    @Override
    public User verificationToken(String token) {
        //1.查看redis 有没有token 没有则证明用户没登录或者长期不活跃自动退出，则需要重新登录
        existToken(token);
        //2.redis存在token 获取token的value即token的有效时间 有效时间-过期时间 <= 失效时间 则抛刷新token异常 让他在调用刷新token接口 刷新token的有效时间 否则需要重新登录
        validateToken(token);
        //3.redis存在token 解析token 获取username 验证用户存不存在 不存在则删除token，重新登录
        return getUser(token);
    }
    /**
     * 查看凭证是否存在
     *
     * @param token 凭证
     */
    private void existToken(String token) {
        try {
            Object o = redisUtils.get(token);
            if (o != null) {
                return;
            }
        } catch (Exception ignored) {
        }

        throw new ExpiredCredentialsException("凭证失效，请重新登录!");

    }

    /**
     * 检验token 过期时间-现在时间 <= 失效时间 则自动刷新token的有效时间 否则需要重新登录
     *
     * @param token 凭证
     */
    private void validateToken(String token) {
        try {
            //剩余过期时间 秒
            long expire = redisUtils.getExpire(token);
            //剩余过期时间大于可允许时间并且大于0，小于0就过期了
            if (expire <= jwtProperties.getAllowRefreshTime() && expire > 0) {
                throw new GlobalException("凭证准备过期!");
            }
        } catch (GlobalException e) {
            throw e;
        } catch (Exception e) {
            log.error("validate token is error:", e);
            throw new ExpiredCredentialsException("凭证失效，请重新登录!");
        }
    }

    /**
     * 刷新有效时间
     *
     * @param token 凭证
     */
    @Override
    public String refreshToken(String token) {
        //shiro登出
        SecurityUtils.getSubject().logout();

        //校验原来的token是否有效
        token = getToken(token);
        Date expirationDate = redisUtils.get(token, Date.class);
        if (ObjectUtils.isEmpty(expirationDate)) {
            removeToken(token);
            throw new GlobalException("凭证已失效!请重新登录!");
        }

        //刷新token
        return refreshNewToken(token);
    }

    /**
     * 刷新凭证 获取凭证原来的信息构建新的凭证 设置新的过期时间 保存在redis中
     *
     * @param token 凭证
     * @return 凭证
     */
    private String refreshNewToken(String token) {
        removeToken(token);
        Claims claimsFromToken = JwtTokenUtil.getClaimsFromToken(secret(), token);
        claimsFromToken.put(JwtTokenUtil.CLAIM_KEY_CREATE_TIME, System.currentTimeMillis());
        return generateToken(claimsFromToken);
    }

    private String generateToken(Claims claimsFromToken) {
        String token = JwtTokenUtil.generateToken(secret(), claimsFromToken);
        if (StringUtils.isBlank(token)) {
            throw new ExpiredCredentialsException("登录失败,请重新登录！");
        }
        saveToken(token);
        return token;
    }

    /**
     * 保存token到redis
     *
     * @param token 凭证
     */
    private void saveToken(String token) {
        redisUtils.set(token, expirationDate(), expirationSecond());
    }

    /**
     * 根据token获取用户信息 不存在则抛ExpiredCredentialsException
     *
     * @param token 凭证
     * @return 用户信息
     * @throws ExpiredCredentialsException 凭证过期异常
     */
    public User getUser(String token) {
        Integer userId = JwtTokenUtil.getUserIdFromToken(secret(), token);
        if (ObjectUtils.isEmpty(userId)) {
            throw new ExpiredCredentialsException("无效凭证，请重新登录!");
        }

        // 查询用户信息
        User user = userService.getUserById(User.builder().id(userId).build());

        if (ObjectUtils.isEmpty(user)) {
            throw new ExpiredCredentialsException("用户不存在,请重新登录!");
        }
        return user;
    }

    @Override
    public String getToken(HttpServletRequest request) {
        String authHeader = request.getHeader(jwtProperties.getHeader());
        return getToken(authHeader);
    }

    @Override
    public String getToken(String token) {
        String tokenPrefix = jwtProperties.getTokenHead();
        if (StringUtils.isNotBlank(token) && token.startsWith(tokenPrefix)) {
            return token.substring(tokenPrefix.length());
        }
        return null;
    }

    @Override
    public void removeToken(String token) {
        redisUtils.del(token);
    }

    /**
     * 获取秘钥
     *
     * @return 秘钥
     */
    private String secret() {
        return jwtProperties.getSecret();
    }

    /**
     * 获取过期时间
     *
     * @return 过期时间 = 当前时间+配置的过期时间
     */
    private Date expirationDate() {
        return DateUtil.offsetSecond(new Date(), expirationSecond());
    }

    /**
     * 获取配置过期秒数
     *
     * @return 过期秒数
     */
    private int expirationSecond() {
        return jwtProperties.getExpiration().intValue();
    }

}
